package com.briup.facade.init;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.briup.common.properties.ZookeeperProperties;
import com.briup.facade.service.IFascadeEvaluateService;
import com.briup.facade.service.IFascadeTrainingService;
import com.briup.model.entity.*;
import com.briup.model.mapper.ModelMapper;
import com.briup.model.vo.MockResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.TreeCache;
import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.StandardServletEnvironment;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * springboot项目启动后，进行自定义的初始化操作
 * @author wood
 */
@Component
public class ApplicationInit implements CommandLineRunner {

    @Value("${server.port}")
    private int port;
    @Autowired
    private Environment env;
    //正则匹配规则
    private static final String REG;
    //正则表达式对象
    private static final Pattern PATTERN;

    static {
        REG = "\\[(.*)\\]";
        PATTERN = Pattern.compile(REG);
    }

    @Autowired
    private CuratorFramework client;

    @Autowired
    private ZookeeperProperties zookeeperProperties;

    @Autowired
    private IFascadeTrainingService fascadeTrainingService;
    @Autowired
    private IFascadeEvaluateService fascadeEvaluateService;
    @Autowired
    private ModelMapper modelMapper;

    @Override
    public void run(String... args) throws Exception {
        showInfo();

        // 启动后对zookeeper的操作
        zookeeperOps();
    }

    private void zookeeperOps()throws Exception {
        // 以下是和zookeeper的交互逻辑
        // 检查结果节点
        checkNode(zookeeperProperties.getResultPath());
        checkNode(zookeeperProperties.getTaskPath());

        // 读取节点上的数据，并处理
        byte[] bytes = client.getData().forPath(zookeeperProperties.getResultPath());
        // 如果数据是null或者不是{开头，那么就退出
        this.processZKData(bytes);

        // 获取TreeCache对象，准备给节点添加监听器
        TreeCache treeCache = new TreeCache(client,zookeeperProperties.getResultPath());

        // 添加监听器
        treeCache.getListenable().addListener((cli, event) -> {

            if (event.getType() == TreeCacheEvent.Type.NODE_UPDATED) {
                // 如果触发事件的节点不是指的节点，那么就方法退出
                if (!event.getData().getPath().equals(zookeeperProperties.getResultPath())) {
                    return;
                }

                byte[] data = event.getData().getData();

                this.processZKData(data);
            }

        });

        treeCache.start();

    }

    // =========================下面是zookeeperOps相关方法===========================
    /**
     * 处理zk事件获取的节点上的数据
     * @param data  节点数据
     * @throws Exception 异常
     */
    private void processZKData(byte[] data)throws Exception{
        // 如果数据是null或者不是{开头，那么就退出
        if(data==null || !new String(data).startsWith("{")){
            return;
        }

        String jsonStr = new String(data);
        JSONObject jsonObject = JSON.parseObject(jsonStr);

        Integer resultType = (Integer)jsonObject.get("resultType");
        ObjectMapper objectMapper = new ObjectMapper();
        MockResult mockResult = objectMapper.readValue(jsonStr, MockResult.class);
        if(resultType!=null){
            switch (resultType){
                case 0:
                    // 初始化训练
                case 1:
                    // 优化训练结果(和初始化训练只有版本不一样，都是调用相同的方法)
                    insertTrainingResult(mockResult);
                    break;
                case 2:
                    // 评估训练结果
                    insertEvaluateResult(mockResult);
                    break;
                default:

            }
        }

        // 数据处理完成后，把Result节点设置为null
        client.setData().forPath(zookeeperProperties.getResultPath(),null);
    }

    /*
     * @Description 评估结果入库
     * @Param [mockResult] 完整评估信息,完整评估分类信息
     **/

    private void insertEvaluateResult(MockResult mockResult) {
        Evaluate evaluate = mockResult.getEvaluate();
        List<EvaluateLabel> evaluateLabelList = mockResult.getEvaluateLabelList();
        // 评估记录入库
        Integer evaluateId = fascadeEvaluateService.insertEvaluate(evaluate);
        // 评估分类入库
        fascadeEvaluateService.insertEvaluateLabel(evaluateId,evaluateLabelList);
        // 评估错误入库
    }

    /*
     * @Description 训练结果入库
     * @Param [mockResult] 完整训练信息,完整训练分类信息
     **/
    private void insertTrainingResult(MockResult mockResult) {
        Training training = mockResult.getTraining();
        List<TrainingLabel> trainingLabelList = mockResult.getTrainingLabelList();
        List<Integer> datasetIdList = mockResult.getDatasetIdList();
        // 将训练信息入库
        Integer trainingId = fascadeTrainingService.insertTraining(training);
        // 将训练分类信息入库
        fascadeTrainingService.insertTrainingLabel(trainingId,trainingLabelList);
        // 填充训练和数据集的中间表
        fascadeTrainingService.insertTrainingDataset(trainingId,datasetIdList);
        // 改变模型状态 4训练完成
        Model model = new Model();
        model.setId(training.getModelId());
        model.setTrainingStatus(4);
        modelMapper.updateByPrimaryKeySelective(model);

    }

    /**
     * 检查节点是否存在，不存在则创建
     * @param nodePath 节点路径
     * @throws Exception 异常
     */
    private void checkNode(String nodePath) throws Exception {
        Stat stat = null;
        stat = client.checkExists().forPath(nodePath);
        if (stat == null) {
            client.create().creatingParentsIfNeeded().forPath(nodePath);
        }
        stat = client.checkExists().forPath(nodePath);
        if (stat == null) {
            client.create().creatingParentsIfNeeded().forPath(nodePath);
        }
    }

    // =========================下面是showInfo相关方法===========================

    private void showInfo(){
        InetAddress address = null;
        try {
            address = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        String host = address.getHostAddress();
        System.out.println("*******************swagger访问地址********************");
        System.out.println("http://"+host+":"+port+"/swagger-ui.html");
        System.out.println("http://xxx.xxx.xxx.xxx:"+port+"/swagger-ui.html");
        System.out.println("*******************swagger访问地址********************");

        System.out.println();

        System.out.println("*****************本次启动激活的profile*****************");
        System.out.println("当前激活的profile: "+ Arrays.toString(env.getActiveProfiles()));
        System.out.println("*****************本次启动激活的profile*****************");

        System.out.println();

        System.out.println("*****************本次启动读取的配置文件*****************");
        StandardServletEnvironment sse = (StandardServletEnvironment) env;
        sse.getPropertySources().forEach(ps->{
            if(ps instanceof OriginTrackedMapPropertySource){
                OriginTrackedMapPropertySource ot = (OriginTrackedMapPropertySource) ps;
                System.out.println(findConfigFile(ot.getName()));
            }
        });
        System.out.println("*****************本次启动读取的配置文件*****************");
    }

    /**
     * 使用正则表达式，查找配置文件，例如 [application.yml]
     * @param line
     * @return
     */
    private static String findConfigFile(String line) {

        Matcher m = PATTERN.matcher(line);

        if(m.find()){
            return m.group();
        }

        return null;

    }

}
